package com.ai.common.utils;
import com.ai.common.utils.bean.ConvertCallback;
import com.ai.common.utils.bean.MyPropertyDescriptor;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ConvertUtil {

    private static final Map<String, BeanCopier> copierCache = new ConcurrentHashMap<>();

    private static final Map<Class<?>, Map<String, PropertyDescriptor>> pdCache = new ConcurrentHashMap<>();

    /**
     * 对象转换
     */
    public static <T> T convert(Object source, Class<T> targetClass) {
        return ConvertUtil.convert(source, targetClass, null);
    }

    /**
     * 对象转换
     */
    public static <T, S> T convert(S source, Class<T> targetClass, ConvertCallback<T, S> callback) {
        if (source == null) {
            return null;
        }
        T t = null;
        try {
            t = targetClass.newInstance();
            if(ConvertUtil.class.getClassLoader().equals(targetClass.getClassLoader())){
                BeanCopier beanCopier = ConvertUtil.getCopier(source.getClass(), targetClass);
                beanCopier.copy(source, t, null);
            }else {
                ConvertUtil.copyWithDifferentClassLoader(source, t);
            }
            if (callback != null) {
                callback.callback(source, t);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return t;
    }

    /**
     * list对象转换
     */
    public static <T> List<T> convertList(List<?> list, Class<T> targetClass) {
        return ConvertUtil.convertList(list, targetClass, null);
    }

    /**
     * list对象转换
     */
    public static <T, S> List<T> convertList(List<S> list, Class<T> targetClass, ConvertCallback<T, S> callback) {
        if (list == null) {
            return null;
        }

        List<T> result = new ArrayList<>();
        try {
            for (S o : list) {
                result.add(ConvertUtil.convert(o, targetClass, callback));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * 对象转换
     */
    public static <T, S> T convertDeep(S source, Class<T> targetClass) {
        return ConvertUtil.convertDeep(source, targetClass, null);
    }

    /**
     * 对象转换
     */
    public static <T, S> T convertDeep(S source, Class<T> targetClass, ConvertCallback<T, S> callback) {
        if (source == null) {
            return null;
        }

        T target = null;
        try {
            target = targetClass.newInstance();
            Collection<PropertyDescriptor> targetPds = ConvertUtil.getPds(targetClass);
            Map<String, PropertyDescriptor> sourcePdMap = ConvertUtil.getPdMap(source.getClass());
            if (CollectionUtils.isEmpty(targetPds) || sourcePdMap.isEmpty()) {
                return target;
            }

            for (PropertyDescriptor targetPd : targetPds) {
                //目标对象没有写方法,跳过
                Method writeMethod = targetPd.getWriteMethod();
                if (writeMethod == null) {
                    continue;
                }

                //源对象没有读方法,跳过
                Method readMethod = null;
                PropertyDescriptor readPd = sourcePdMap.get(targetPd.getName());
                if (readPd == null || (readMethod = readPd.getReadMethod()) == null) {
                    continue;
                }

                //目标类型是非源类型或原类型的基类,转换
                if (!ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
                    Object writeValue = ConvertUtil.convertDeep(readMethod.invoke(source), writeMethod.getParameterTypes()[0]);
                    writeMethod.invoke(target, writeValue);
                    continue;
                }

                //如果是list
                if (ClassUtils.isAssignable(List.class, writeMethod.getParameterTypes()[0])) {
                    ConvertUtil.convertList(writeMethod, readMethod, source, target);
                    continue;
                }

                writeMethod.invoke(target, readMethod.invoke(source));

                if (callback != null) {
                    callback.callback(source, target);
                }
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return target;
    }

    /**
     * list对象转换
     */
    public static <T, S> List<T> convertListDeep(List<S> list, Class<T> targetClass) {
        return ConvertUtil.convertListDeep(list, targetClass, null);
    }

    /**
     * list对象转换
     */
    public static <T, S> List<T> convertListDeep(List<S> list, Class<T> targetClass, ConvertCallback<T, S> callback) {
        if (list == null) {
            return null;
        }

        List<T> result = new ArrayList<>();
        for (S o : list) {
            try {
                result.add(ConvertUtil.convertDeep(o, targetClass, callback));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return result;
    }

    private static <T, S> void convertList(Method writeMethod, Method readMethod, S source, T target) throws Exception {
        Type genericWriteType = writeMethod.getGenericParameterTypes()[0];
        if (!(genericWriteType instanceof ParameterizedType)) {
            writeMethod.invoke(target, readMethod.invoke(source));
            return;
        }

        Class<?> targetActualClass = Class.forName(((ParameterizedType) genericWriteType).getActualTypeArguments()[0].getTypeName());
        Type genericReadType = readMethod.getGenericReturnType();
        if (genericReadType instanceof ParameterizedType) {
            Class<?> sourceActualClass = Class.forName(((ParameterizedType) genericReadType).getActualTypeArguments()[0].getTypeName());
            if (ClassUtils.isAssignable(targetActualClass, sourceActualClass)) {
                writeMethod.invoke(target, readMethod.invoke(source));
                return;
            }
        }

        List<?> writeValue = ConvertUtil.convertListDeep((List<?>) readMethod.invoke(source), targetActualClass);
        writeMethod.invoke(target, writeValue);
    }

    private static Collection<PropertyDescriptor> getPds(Class<?> clazz) throws Exception{
        return ConvertUtil.getPdMap(clazz).values();
    }

    private static Map<String, PropertyDescriptor> getPdMap(Class<?> clazz) throws Exception {
        Map<String, PropertyDescriptor> pdMap = pdCache.get(clazz);
        if (pdMap != null) {
            return pdMap;
        }

        PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(clazz);
        pdMap = new ConcurrentHashMap<>();
        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            Method readMethod = propertyDescriptor.getReadMethod();
            if (readMethod != null && !Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                readMethod.setAccessible(true);
            }
            Method writeMethod = propertyDescriptor.getWriteMethod();
            if (writeMethod != null && !Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                writeMethod.setAccessible(true);
            }

            if(readMethod == null && writeMethod != null){
                propertyDescriptor = getIsReadMethodPd(propertyDescriptor, clazz);
            }

            pdMap.put(propertyDescriptor.getName(), propertyDescriptor);
        }
        pdCache.put(clazz, pdMap);
        return pdMap;
    }

    private static PropertyDescriptor getIsReadMethodPd(PropertyDescriptor pd, Class<?> clazz) throws IntrospectionException {
        Class<?> propertyType = pd.getPropertyType();
        if (!Boolean.class.equals(propertyType)){
            return pd;
        }
        String name = pd.getName();
        StringBuilder builder = new StringBuilder(name);
        builder.setCharAt(0, Character.toUpperCase(builder.charAt(0)));
        String getterName = "is" + builder;
        Method readMethod = ReflectionUtils.findMethod(clazz, getterName);
        if (readMethod == null) {
            return pd;
        }

        if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
            readMethod.setAccessible(true);
        }

        return new MyPropertyDescriptor(clazz, pd.getName(), readMethod,
                pd.getWriteMethod(), pd.getPropertyEditorClass());
    }

    private static BeanCopier getCopier(Class<?> sourceClass, Class<?> targetClass) {
        String key = sourceClass.getName() + "-" + targetClass.getName()
                + "_" + sourceClass.getClassLoader() + "_" + targetClass.getClassLoader();
        if (copierCache.containsKey(key)) {
            return copierCache.get(key);
        }

        BeanCopier copier = BeanCopier.create(sourceClass, targetClass, false);
        copierCache.put(key, copier);
        return copier;
    }

    private static <S, T> void copyWithDifferentClassLoader(S source, T target) {
        BeanUtils.copyProperties(source, target);
    }
}
